Résolveur DNS public sur TLS (DoT) et HTTPS (DoH) : dns.shaftinc.fr

Sommaire

Politique de dns.shaftinc.fr et configuration client

Politique

Configuration client

DoT

DoH

Documentation technique du service dns.shaftinc.fr

Everything-over-TLS

Unbound

dnsdist

Supervision

Blog de Shaft

Politique de dns.shaftinc.fr et configuration client

Cette page décrit la politique du résolveur dns.shaftinc.fr et documente (partiellement) comment l'utiliser.

Politique

dns.shaftinc.fr fourni un service DNS sur TLS (DoT) et DNS sur HTTPS (DoH). Ces 2 techniques permettent d'avoir un canal sécurisé avec le résolveur DNS, les requêtes sont donc protégées de l'écoute d'un tiers, mais pas contre la personne gérant le service (moi en l'occurence). L'utiliser revient donc à me faire confiance, à l'aune de cette politique ainsi qu'à l'évaluation de cette dernière.

Le résolveur dns.shaftinc.fr :

Configuration client

Cette documentation est en partie reprise d'un précédent billet sur le sujet, mais adaptée à l'utisation de dns.shaftinc.fr. Je ne documente qu'un type d'utilisation (qui me semble le plus adapté), libre à vous d'utiliser d'autres outils si vous le souhaitez. Dans l'idéal, je considère qu'il faut privilégier l'utilisation de DoT et se rabattre sur DoH si cela est impossible (port 853 bloqué par exemple).

DoT

Linux (et Windows, Mac…)

Le meilleur client disponible est de loin Stubby. Simple à configurer, à jour… Il a ceci dit un principal défaut : en tant que résolveur minimum, il ne dispose pas de cache. Pour pallier à cela, on va lui adjoindre Unbound, qui lui possède un cache (historiquement, la partie TLS et TCP d'Unbound est très faible quand il est utilisé comme forwarder, cela s'est normalement amélioré depuis la version 1.13 avec la possibilité de réutiliser des connections TCP. Stubby garde néanmoins l'avantage avec la gestion de TCP Fast Open — disponible sous Unbound mais il doit être compilé avec l'option, ce qui n'est pas le cas sous Debian notamment, la possibilité d'utiliser plusiuers résolveurs...). Unbound possède plein de chouettes options outre son cache (gérer un domaine local, bloquer les pubs et trackers…). Le fonctionnement est le suivant : sur la machine locale, Unbound est le résolveur utilisé par le système mais il transmet toutes les requêtes à Stubby qui va gérer la partie TLS et discuter avec le serveur DoT, ce qui donne le schéma suivant :

 
 

Il faut donc installer donc les 2 logiciels :

                # apt-get install unbound stubby

               

Configurons Stubby. Il faut éditer /etc/stubby/stubby.yml. Dès l'installation, toute la configuration TLS est pré-réglée correctement et quelques serveurs DoT sont déjà configurés dans la section DEFAULT UPSTREAMS. Commenter l'ensemble des entrées (à moins que vous ne souhaitez utiliser un des résolveurs proposés) et mettre les informations relatives au résolveur pour une authentication stricte. Attention ceci dit, Stubby utilise la syntaxe YAML, il faut donc respecter l'indentation particulière (2 espaces ici) :

upstream_recursive_servers:

                  - address_data: 2001:bc8:2c86:853::853

                  tls_auth_name: "dns.shaftinc.fr"

                  tls_pubkey_pinset:

                    - digest: "sha256"

                    value: ilee9nHBVT0DVWER1VDA+0NCaYd25zVvP0C1Jb4gCIc=

À noter que l'option tls_auth_name, qui permet d'authentifier le résolveur via le nom présenté dans le certificat (méthode par ADN ou Authentication Domain Name pour reprendre les termes du RFC 8310), est optionnelle dans la mesure où l'on fourni également le SPKI via tls_pubkey_pinset. Ceci dit, cela apporte une deuxième authentication, ce qui est appréciable en terme de sécurité. Stubby n'est malhereusement pas capable à l'heure actuelle d'utiliser DANE.

Ne reste plus qu'à lui donner le port d'écoute d'où viendront les requêtes d'Unbound :

listen_addresses:

                  - 127.0.0.1@8053

                  - 0::1@8053

Le port est choisi au hasard, et un autre peut être pris. L'important est de donner le même à Unbound.

Comme le montre la configuration par défaut, Stubby est capable d'avoir plusieurs serveurs DoT configurés en même temps, et il est donc possible d'utiliser dns.shaftinc.fr avec d'autres serveurs. Dans ce cas là, il envoie les requêtes à chaque serveur et prend la première réponse qui arrive (technique de round robin). Le comportement de Stubby se configure alors avec le paramètre round_robin_upstreams.

Configurons ensuite Unbound. Sous Debian & dérivés, créer un fichier .conf dans /etc/unbound/unbound.conf.d/ (Sous Arch & cie, éditer directement /etc/unbound/unbound.conf ou créer un autre fichier et dire à Unbound de le charger via la directive include dans son fichier de configuration principal — Attention, dans ces terres Unbound est probablement chrooté. Ce n'est pas le cas sous Debian). Peu de paramètres à passer : on l'autorise d'abord à interroger le localhost (ce qu'il n'est pas capable de faire par défaut, étant censé être à l'écoute sur 127.0.0.1 et ::1), puis on lui indique les adresses et ports où écoute Stubby afin de lui transmettre les requêtes. Ce qui donne :

server:

                        do-not-query-localhost: no

                forward-zone:

                        name: "."

                        forward-addr: 127.0.0.1@8053

                        forward-addr: ::1@8053

En l'état, cette configuration ne sera utile que sur la machine où elle est installée. Pour en faire profiter le réseau local, il faut ajouter quelques paramètres à Unbound. La section server: devient :

server:

                        do-not-query-localhost: no

                        interface: 0.0.0.0@53

                        interface: ::0@53

                        access-control: 192.168.0.0/24 allow

                        access-control: 2001:db8:cafe:cafe::/64 allow

Les IPs sont bien évidemment à ajuster en fonction de la configuration locale. Pensez aussi à ouvrir le port 53 (TCP et UDP) dans votre pare-feu pour accepter les requêtes venant des machines du réseau. Il est également possible de ne pas écouter sur toutes les adresses disponibles, auquel cas il faut bien penser à ajouter le localhost dans la liste des interfaces :

server:

                        ...

                        interface: 127.0.0.1@53

                        interface: ::1@53

                        interface: 192.168.0.1@53 # L'adresse IPv4 locale souhaitée

                        interface: 2001:db8:cafe:cafe::1@53 # L'adresse IPv6 souhaitée

                        ...

Une fois ces 2 logiciels configurés, ne reste plus qu'à :

Android et dérivés

Android embarque un client DoT depuis la version 9 (Pie). Pour l'utiliser, se rendre dans les paramètres réseaux avancés (ou chercher DNS dans le champ de recherche des paramètres. La configuration ressemble à ceci :

 
 

Malheureusement, vu le taux de déploiement minable d'IPv6 chez les FAIs mobile, il serait étonnant que dns.shaftinc.fr soit disponible. Il reste d'autre résolveurs DoT disponible (ns0.ldn-fai.net, dot.bortzmeyer.fr, et plein d'autres encore).

personalDNSfilter

Terminons cette section DoT en mentionnant une application Android plus sympathique que les paramères systèmes. personalDNSfilter est un bloqueur de publicité disponible pour Android et qui, comme son nom l'indique judicieusement, utilise le DNS pour parvenir à ses fins. Pour ce faire, il crée un VPN local par lequel vont transiter les requêtes en les filtrant via des listes prédéfinis (il est bien évidemment possible de gérer ces listes et d'en ajouter ou en enlever). Ce logiciel présente 2 avantages :

L'interface n'est pas la plus belle au monde, mais la configuration est relativement simple. Si vous êtes sous une version récente d'Android, la première étape consiste à vérifier que le système est configuré pour utiliser le résolveur fourni par le réseau :

 
 

Dans l'application, cliquer sur le bouton d'édition à côté de l'adresse IP :

 
 

De là, activer l'option Désactiver la découverte... et supprimer le bloc d'adresse de résolveur DoT configurés par défaut :

 
 

Il n'y a plus qu'à renseiger les infos de dns.shaftinc.fr :

[2001:bc8:2c86:853::853]::853::DOT::dns.shaftinc.fr

 
 

Il est possible d'utiliser DoH à la place :

[2001:bc8:2c86:853::853]::443::DOH::https://dns.shaftinc.fr/

Une fois configuré, ne reste qu'à appuyer sur le bouton Redémarrer dans l'application. Afin de vérifier que tout fonctionne, il doit y avoir une petite clé dans la zone de notification du système et une notification (sous Android 9 tout du moins) :

 
 

À noter que je n'ai testé personalDNSfilter que sous Android 9 et 10 (enfin /e/, mais sous ces bases). Je suis preneur de retours sur des versions inférieures de l'OS (il est compatible Android 4.0+). Les captures ont été faites sous Android 9, le thème sombre s'améliore un peu sous Android 10 en terme de lisibilité.

DoH

Pour les machines de bureaux, les principaux clients DoH sont pour l'instant les navigateurs Web. Il est prévu que Stubby soit compatible dans une future version. En attendant, cette documentation traitera le cas de Firefox.

Sous Firefox, au moins depuis la version 68 et peut-être avant — les notes de versions de Firefox ne sont pas un modèle d'exhaustivité — le paramétrage de DoH se fait dans la section Paramètres réseaux des Préférences :

 
 

Il n'y a plus qu'à activer DNS sur HTTPS, choisir un fournisseur personnalisé et entrer l'URL : https://dns.shaftinc.fr/ :

 
 

Par défaut, Firefox est configuré pour se replier sur le résolveur du système si la résolution DoH échoue. Pour passer en mode strict — sans repli donc — il faut se rendre dans about:config et passer le paramètre network.trr.mode à 3. Il faut également indiquer l'adresse du résolveur (pour rappel : 2001:bc8:2c86:853::853) dans le paramètre network.trr.bootstrapAddress. Ce dernier est facultatif depuis Firefox 74, mais je le conseille vivement. Au final, on a :

 
 

Chrome est compatible DoH depuis la version 83 (sortie en mai 2020) sous Windows et macOS. Sous Android aussi depuis fin 2020 normalement. J'aurai voulu faire un petit guide pour ce navigateur, mais dans sa version 92.0.4515.159 du 16 août 2021, DoH n'est toujours pas inclus dans sa version Linux... Pas de moyen de tester par moi-même donc. Si j'en crois les gens de Google, il faut aller dans les paramètres, chercher DNS et bien choisir Personnalisé sous peine de voir ces requêtes partir sur 8.8.8.8 et non le résolveur souhaité.

Première rédaction le 23 août 2021. Dernière modification le 31 août 2021.

Contenu sous licence CC-BY-NC-SA.

 

Documentation technique du service dns.shaftinc.fr

Everything-over-TLS

Après des mois de procrastination bêta-tests privés, je lance enfin mon résolveur DNS sur TLS (DoT) et DNS sur HTTPS (DoH) public : dns.shaftinc.fr. Pour la politique suivie par ce résolveur et la configuration côté client, voir la page dédiée à ces questions. Ce billet traite de la configuration du serveur, au cas où des gens veulent également se lancer dans l'aventure. Attention, billet long et technique.

dns.shaftinc.fr utilise 2 logiciels : Unbound et dnsdist. dnsdist est un répartiteur de charge pour serveurs DNS avec la particularité de gérer DoH et DoT. Le but est donc de l'installer sur la même machine qu'Unbound et de le mettre devant : Unbound n'écoutera que localement et dnsdist, lui, sera ouvert au public. Il est bien évidemment possible d'utiliser un autre résolveur (Knot Resolver, PowerDNS Recursor...) ou bien d'utiliser un résolveur distant (sous votre contrôle de préférence). Le principe général étant posé, voyons comment configurer le tout. Commme d'habitude sur ce blog, les exemples sont valables pour Debian (Bullseye minimum, la version de dnsdist dans Buster est trop vieille) et ses dérivés.

Unbound

La configuration d'Unbound est dérivée de celle que j'utilise pour mon réseau personnel, en augmentant un peu la taille des caches. Elle n'entre finalement pas tellement dans le cadre de ce billet, il n'y a pas vraiment de paramètres spécifiques à passer pour le brancher à dnsdist. Pour les curieux·ses la configuration est consultable par ici. À noter que la taille des caches est sans doute un peu grande, elle diminuera peut-être avec plus de recul.

dnsdist

La base de la configuration est tirée de celle documentée par Stéphane Bortzmeyer, complétée par la lecture de la (très complète) documentation du logiciel. Le fichier qui nous intéresse est /etc/dnsdist/dnsdist.conf. Sous Debian, il doit comporter le paramètre setSecurityPollSuffix("") : il s'agit de la désactivation d'un contrôle de sécurité de la version ajoutée par les personnes en charge de la maintenance du paquet chez Debian. Si ce paramètre revient à sa valeur par défaut (contôle actif), dnsdist plante au démarrage.

Commençons par créer le serveur DoT :

-- DoT

                addTLSLocal(

                        "[2001:bc8:2c86:853::853]:853",

                        "/etc/dnsdist/certificat.pem",

                        "/etc/dnsdist/cléprivée.key",

                        {

                        provider="openssl",

                        minTLSVersion="tls1.2",

                        ciphers="ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384",

                        ciphersTLS13="TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384",

                        ocspResponses={"/etc/dnsdist/dnsshaftinc.oscp"},

                        tcpFastOpenQueueSize=256

                        }

                )

               

Les premiers paramètres sont explicites : on indique sur quelle adresse et port écouter puis on indique le chemin vers le certificat X.509 et la clé privée. Attention toutefois, sous Debian, dnsdist est très restreint en capacité afin de protéger le système (jetez un œil au fichier de service systemd), clé et certificat doivent donc être dans un répertoire accessible par le logiciel (/etc/dnsdist) et doivent être lisible pour l'utilisateur _dnsdist.

On passe ensuite les options que l'on souhaite. Les 4 premières sont explicites (à noter que dnsdist est agnostique et peut utiliser GnuTLS comme fournisseur — au prix de quelques modifications de paramétrage), mais les 2 dernières sont plus obscures et nécessitent quelques explications. Elles sont par ailleurs totalement optionnelles (et je ne suis pas sûr que tout les clients soient en mesures d'utiliser ces techniques).

ocspResponses permet d'utiliser l'OCSP Stapling ou agafrage OCSP. Acronyme de Online Certificate Status Protocol et défini dans le RFC 6960, OCSP est une technique utilisée lors de l'établissement d'une connexion TLS et permettant à un client de vérifier le statut d'un certificat X.509 auprès de l'autorité de certification l'ayant signé. Cette dernière répond si le certificat en question est révoqué ou non. Le problème de ce protocole est qu'il ralenti l'établissement de la connexion TLS (il faut demander des choses à l'autorité de certification) et surtout pose un souci de vie privée (l'autorité connaît de fait les domaines que vous visitez). Pour palier à ces deux problèmes, on permet au serveur d'envoyer directement la réponse OCSP au client lors de la poignée de main TLS via l'agrafage (stapling donc) de cette dernière. Pour ce faire le serveur demande cette réponse à l'autorité de certification, cette dernière nous en donne une valable en général quelques jours et signée cryptographiquement afin d'éviter les problèmes, et il ne reste plus qu'à la fournir au client. Les serveurs Web les plus courants (Nginx & Apache) ont des mécanismes pour faire tout cela automatiquement, ce n'est pas le cas de dnsdist, ce qui demande quelques manipulations.

La technique suivante est reprise de la documentation de dnsdist. Dans un premier temps, il faut récupérer l'URL du serveur OCSP de l'autorité de certification. Ce dernier est présent dans le certificat et on le récupère avec OpenSSL :

                # openssl x509 -noout -ocsp_uri -in /etc/dnsdist/certificat.pem

               

Avec une autorité comme Let's Encrypt, la réponse devrait-être quelque chose comme http://r3.o.lencr.org. Ne reste plus qu'à interroger ladite autorité et écrire sa réponse dans un fichier, toujours avec OpenSSL :

                # openssl ocsp -no_nonce -issuer /chemin/vers/certificat/autorité/certification -cert /etc/dnsdist/certificat.pem -text -url url/trouvé/ci/dessus -respout /etc/dnsdist/dnsshaftinc.oscp

               

Si vous utilisez le client ACME certbot, le certificat de l'autorité s'obtient en passant le paramètre --chain-path /chemin/vers/... lors de la création ou le renouvellement du certificat.

L'autorité doit répondre un gros pâté de ce type :

                OCSP Request Data:

                    Version: 1 (0x0)

                    Requestor List:

                    ...

                OCSP Response Data:

                    OCSP Response Status: successful (0x0)

                    Response Type: Basic OCSP Response

                    Version: 1 (0x0)

                    Responder Id: C = US, O = Let's Encrypt, CN = R3

                    Produced At: Aug 10 22:45:00 2021 GMT

                    Responses:

                    Certificate ID:

                      Hash Algorithm: sha1

                      Issuer Name Hash: 48DA...

                      Issuer Key Hash: 142E...

                      Serial Number: 0415...

                    Cert Status: good

                    This Update: Aug 10 22:00:00 2021 GMT

                    Next Update: Aug 17 22:00:00 2021 GMT

 

                    Signature Algorithm: sha256WithRSAEncryption

                         8f:0a:...

 

                Response verify OK

                ...

               

Dans la réponse nous avons bien le statut 0x0 indiquant un succès et l'on remarque que la réponse est valable une semaine. De ce que j'ai pu constater avec Let's Encrypt, elle est en réalité renouvellée tous les 3 jours. Dans tous les cas, il faut mettre à jour notre fichier automatiquement, sous peine de se retrouver avec une réponse périmée. Le plus simple est de faire un script et de le déclencher quotidiennement via la crontab. Cela donne :

#!/bin/bash

 

                ocspURL=$(openssl x509 -noout -ocsp_uri -in /etc/dnsdist/certificat.pem)

                openssl ocsp -no_nonce -issuer /chemin/vers/certificat/autorité/certification -cert /etc/dnsdist/certificat.pem -text -url $ocspURL -respout /etc/dnsdist/dnsshaftinc.oscp

 

                chown _dnsdist: /etc/dnsdist/dnsshaftinc.oscp

 

                # On recharge le tout dans dnsdist

                dnsdist -e "reloadAllCertificates()"

Pour vérifier que l'ensemble fonctionne bien une fois en place, on peut utiliser OpenSSL :

                $ openssl s_client -connect dns.shaftinc.fr:853 -status | grep OCSP

                ...

                OCSP response:

                OCSP Response Data:

                    OCSP Response Status: successful (0x0)

                    Response Type: Basic OCSP Response

               

Encore une fois, nous avons le statut 0x0, tout fonctionne.

Attention à bien regénérer ce fichier à chaque changement de certificat, et à bien lancer la commande

                dnsdist -e "reloadAllCertificates()"

à chaque modification du certificat, de la clé privée ou du fichier OCSP.

Le deuxième option obscure était tcpFastOpenQueueSize. TCP Fast Open (ou TFO, décrit dans le RFC 7413) est une méthode permettant d'accélerer l'établissement d'une connexion TCP. Normalement, une connexion TCP s'établie via la fameuse triple poignée de main :

C'est relativement lent, surtout qu'ensuite dans le cadre de DoH et DoT il faut établir la connexion TLS. Pour accélérer l'ensemble, TFO propose au client d'envoyer des données dès le premier paquet SYN. Pour faire simple, la première connexion à un serveur gérant TFO se fait toujours de la manière usuelle, mais si le client a indiqué connaître TFO (via une option dans le premier paquet SYN), alors le serveur fourni au passage un petit cookie qu'il a généré. Pour les connexions suivantes, le client n'a qu'à fournir ce cookie dans son premier paquet SYN et y mettre directement des données, dans le cas qui nous intéresse un ClientHello de TLS.

TFO est entièrement implémenté dans le noyau Linux depuis la version 3.16 et il est actif en tant que client depuis la même période a peu près (un peu avant, le noyau 3.16 apporte juste TFO pour IPv6). Il se configure via le paramètre net.ipv4.tcp_fastopen. Ce dernier accepte les valeurs suivantes :

Pour dns.shaftinc.fr, je suis parti sur le mode client & serveur. Pour l'activer, on passe la commande :

                $ sudo sysctl -w net.ipv4.tcp_fastopen=3

               

On vérifie au besoin que c'est pris en compte :

                $ sudo sysctl -a --pattern "fastopen"

                net.ipv4.tcp_fastopen = 3

                ...

               

Le problème de la commande sysctl est que le changement de paramètre ne survivra pas au redémarrage de la machine. Pour palier à ça, on va créer un fichier /etc/sysctl.d/10-tcp-fastopen.conf (le numéro peut être différent) et y mettre tout simplement :

net.ipv4.tcp_fastopen=3

Pour revenir à notre paramètre tcpFastOpenQueueSize, il sert juste à dire à dnsdist de conserver les données pour 256 clients. J'avoue avoir choisi la valeur un peu au hasard, n'ayant pas de recul. Elle pourra peut-être évoluer.

Le serveur DoT est configuré, passons à DNS over HTTPS. Les paramètrages sont identiques, à quelques excpetions liées à HTTP :

-- DoH

                addDOHLocal(

                                "[2001:bc8:2c86:853::853]:443",

                                "/etc/dnsdist/certificat.pem",

                                "/etc/dnsdist/cléprivée.key",

                                {"/", "/about", "/politique", "/config"},

                                {

                                provider="openssl",

                                minTLSVersion="tls1.2",

                                ciphers="ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384",

                                ciphersTLS13="TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384",

                                ocspResponses={"/etc/dnsdist/dnsshaftinc.oscp"},

                                customResponseHeaders={

                                                ["Expect-CT"]="max-age=604800, enforce",

                                                ["Strict-Transport-Security"]="max-age=31536000",

                                                ["link"]="<https://www.shaftinc.fr/dns-shaftinc.html> rel=\"service-meta\"; type=\"text/html\""

                                },

                                tcpFastOpenQueueSize=256

                        }

                )

 

                -- Liens externes

                helppages = {

                newDOHResponseMapEntry("^/about$", 308, "https://www.shaftinc.fr/dns-shaftinc.html"),

                newDOHResponseMapEntry("^/politique$", 308, "https://www.shaftinc.fr/dns-shaftinc.html#politique"),

                newDOHResponseMapEntry("^/config$", 308, "https://www.shaftinc.fr/dns-shaftinc.html#configuration")

                }

 

                dohFE = getDOHFrontend(0)

                dohFE:setResponsesMap(helppages)

Le paramétrage est donc sensiblement similaire. Notons que l'on peut utiliser une clé privée et donc un certificat différents pour le serveur DoT. Les différences viennent de la ligne :

{"/", "/about", "/politique", "/config"},

Le premier chemin est celui que les clients utiliseront pour interroger le serveur (si on avait mis par exemple "/query", l'URL du serveur aurait été https://dns.shaftinc.fr/query). Les 3 autres sont des pages d'aides, défini dans la section en dessous comme étant des redirections. À noter également l'ajout de quelques entêtes HTTP, notamment afin d'améliorer la sécurité (Expect-CT et Strict-Transport-Security).

On ajoute ensuite les autorisations et limitations, reprises de la configuration de Stéphane Bortzmeyer (hormis pour la limitation de requêtes par seconde, montée à 200 :

-- ACL

 

                addACL("[::]/0")

 

                -- Limitation à 200 QpS

                addAction(MaxQPSIPRule(200), DropAction())

 

                -- Limitations de connexions

 

                setMaxUDPOutstanding(65535)     -- Nombre maximum de requêtes en attente pour un résolveur

                setMaxTCPClientThreads(30)      -- Nombre maximum de fils d'exécution TCP (chacun pouvant traiter plusieurs clients)

                setMaxTCPConnectionDuration(1800) -- Après trente minutes, on raccroche

                setMaxTCPQueriesPerConnection(300) -- Après trois cents requêtes, on raccroche

                setMaxTCPConnectionsPerClient(10) -- Dix connexions par client (élévé mais il faut penser à des choses comme le CG-NAT)

 

                -- Cache

                pc = newPacketCache(100000)

                getPool(""):setCache(pc)

Pour le cache, la documentation de dnsdist précise que pour une machine dotée de 8 Gio de RAM et en considérant la taille moyenne d'une réponse à 512 octets, un cache de 1 000 000 d'entrées est une estimation raisonnable. La machine hébergeant dns.shaftinc.fr n'a que 4 Gio de RAM, fait tourner d'autres services consommateurs de mémoire (dont Unbound)... donc dans un premier temps, 100 000 entrées maximum dans le cache semble plus que correct.

On configure ensuite le serveur Web interne et la console. Ils serviront à sortir des statistiques ou bien passer des commandes à dnsdist via la console (pour recharger le certificat X.509 par exemple) :

-- Webserver

 

                webserver("127.0.0.1:8083", "super pharase de passe compliquée", "clé pour l'API compliquée aussi")

 

                -- Console

                controlSocket('[::1]:5199')

                setKey("phrase de passe super balaise")

Attention à bien suivre la procédure pour générer la clé de la console.

Tout est presque en place, ne manque plus qu'à donner à dnsdist un ou plusieurs résolveurs avec qui discuter. Pour dns.shaftinc.fr, dnsdist n'est connecté qu'avec le Unbound local, mais le logiciel étant un répartiteur de charge, il est possible d'en ajouter plusieurs et appliquer une politique de répartition. En n'utilisant qu'un serveur local la configuration est au final simple :

newServer({address="[::1]:53", useClientSubnet=false, name="Unbound"})

Supervision

Pour superviser dnsdist, l'utilitaire getdns_server_mon présent dans le paquet getdns-utils permet de faire des tests pour DoT proposant une sortie conforme à ce qu'attendent Nagios & dérivés. Attention le logiciel à une syntaxe lourde. Pour tester le RTT par exemple :

$ getdns_server_mon -M -K 'pin-sha256="ilee9nHBVT0DVWER1VDA+0NCaYd25zVvP0C1Jb4gCIc="' -S -T @2001:bc8:2c86:853::853#853~dns.shaftinc.fr rtt 2500,3000 . SOA

DNS SERVER OK - RTT lookup succeeded in 23ms

               

Détaillons un peu les paramètres de cette commande :

La commande pourrait être allégée, par exemple en n'utilisant un profil opportuniste et donc en authentifiant pas la connexion, mais cela perd en intéret. L'aide de getdns_server_mon détaille l'ensemble des tests possibles. Ceux qui me semblent utiles sont :

Et de manière plus occasionnelle, les tests suivants, qui permettent notamment de vérifier que les résolveurs avec lesquelles dnsdist ont bien les fonctionnalités attendues :

Voici une commande pour Icinga 2 utilisant getdns_server_mon. Elle comporte toutes les options à l'exception de -D (mode debug) et -V (affiche la version) :

object CheckCommand "dot" {

                        import "plugin-check-command"

                        command = [ "/usr/bin/getdns_server_mon", "-M", "-v" ]

                        arguments += {

                                "-E" = {

                                        description = "Fail on DNS error (NXDOMAIN, SERVFAIL)"

                                        set_if = "$getdns_fail_dns_error$"

                                }

                                "-K" = {

                                        description = "SPKI pin for TLS connections (can repeat)"

                                        repeat_key = true

                                        value = "$getdns_spki$"

                                }

                                "-S" = {

                                        description = "Use strict profile (require authentication)"

                                        set_if = "$getdns_strict_auth$"

                                }

                                "-T" = {

                                        description = "Use TLS transport"

                                        set_if = "$getdns_tls$"

                                }

                                "-t" = {

                                        description = "Use TCP transport"

                                        set_if = "$getdns_tcp$"

                                }

                                "-u" = {

                                        description = "Use UDP transport"

                                        set_if = "$getdns_udp$"

                                }

                                test = {

                                        description = "Test to apply"

                                        order = 98

                                        required = true

                                        skip_key = true

                                        value = "$getdns_test$"

                                }

                                test_params = {

                                        description = "Optionnal params for tests"

                                        order = 99

                                        skip_key = true

                                        value = "$getdns_test_param$"

                                }

                                upstream = {

                                        description = "@<ip>[%<scope_id>][@<port>][#<tls_port>][~<tls name>][^<tsig spec>]"

                                        order = 97

                                        required = true

                                        skip_key = true

                                        value = "$getdns_upstream$"

                                }

                        }

                }

Et on peut ensuite créer des services, voici celui effectuant le test lookup :

object Service "DoT - Lookup" {

                        host_name = "dns.shaftinc.fr"

                        check_command = "dot"

                        check_timeout = 16s

                        vars.getdns_fail_dns_error = true

                        vars.getdns_spki = "pin-sha256=\"ilee9nHBVT0DVWER1VDA+0NCaYd25zVvP0C1Jb4gCIc=\""

                        vars.getdns_strict_auth = true

                        vars.getdns_test = "lookup"

                        vars.getdns_test_param = [ "d.nic.fr", "AAAA" ]

                        vars.getdns_tls = true

                        vars.getdns_upstream = "@2001:bc8:2c86:853::853#853~dns.shaftinc.fr"

                }

À noter que l'ensemble a été généré avec le module Director d'Icinga 2 (et que je suis plutôt débutant pour tout ce qui touche à Icinga ☺).

Pour la surveillance de DoH, j'utilise le plugin check_doh présenté dans le billet de Stéphane Bortzmeyer. Il faut aussi penser à surveiller la date d'expiratuion du certificat. Le plugin check_ssl_cert inclut dans le paquet monitoring-plugins-contrib fait parfaitement l'affaire.

Pour terminer cette section sur la surveillance/supervision, dnsdist est capable de se brancher à Carbon (non testé) ou parler SNMP (non testé également). Par ailleurs, le webserver interne est capable de cracher de la statisque au format JSON en interrogeant notamment /jsonstat?command=stats et /api/v1/servers/localhost :

                $ curl -s --header "X-API-Key: 8DjQ..." http://127.0.0.1:8083/jsonstat?command=stats | jq

                {

                        "acl-drops": 0,

                        "cache-hits": 1076,

                        "cache-misses": 447,

                        "cpu-iowait": 130965,

                        "cpu-steal": 0,

                        ...

                }

                $ curl -s --header "X-API-Key: 8DjQ..." http://127.0.0.1:8083/api/v1/servers/localhost | jq

                "cache-hit-response-rules": [],

                "daemon_type": "dnsdist",

                "dohFrontends": [

                  {

                        "address": "[2001:bc8:2c86:853::853]:443",

                        "bad-requests": 2,

                        "error-responses": 0,

                        "get-queries": 327,

                        ...

                  }

               

Il est par exemple possible de récupérer ce qui nous intéresse et le donner à manger à Munin notamment. Voici par exemple un graphe Munin montrant le nombre d'entrées dans le cache :

 
 

Le script Munin que j'ai fabriqué est trop spécifique à mon installation (et un peu trop sale aussi) pour être partagé, mais si vous arrivez à manipuler du JSON avec jq ou les outils de votre langage préféré, cela ne devrait pas poser trop de soucis à faire. Encore une fois la documentation de dnsdist est très bien faite.

Première rédaction le 23 août 2021. Dernière modification le 23 août 2021.

Contenu sous licence CC-BY-NC-SA.